/*
 * Created by Spear (http://www.codernet.es), spear@codernet.es
 * Coordinator of XNA Community (http://www.codeplex.com/XNACommunity)
 * 
 */

using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using XMas;

namespace XMasContent
{
    [ContentImporter(".csf", DisplayName = "XMAS - Cald3DImporter", CacheImportedData = true, DefaultProcessor = "CCald3DProcess")]
    public class CCald3DImporter : ContentImporter<NodeContent>
    {   
        public override NodeContent Import(string filename, ContentImporterContext context)
        {
            //System.Diagnostics.Debugger.Launch();
            XmasContent.GetInstance().SetContextImporter(context);
            XmasContent.GetInstance().warn("Import begin...", false);

            NodeContent rootNode = new NodeContent();
            // Model identity is tied to the file it is loaded from
            rootNode.Identity = new ContentIdentity(filename);
            string directory = Path.GetDirectoryName(filename);

            string[] skeleton_files = Directory.GetFiles(directory, "*.csf");
            Cal3DSkeletonParser skeleton_parser = new Cal3DSkeletonParser();
            if (skeleton_files.Length == 1)
            {
                skeleton_parser.load(skeleton_files[0], context);
            }
            else
            {
                throw new InvalidContentException("Skeleton file not found or too much files in the same directory\n");
            }

            string[] meshes_files = Directory.GetFiles(directory, "*.cmf");
            for (int i = 0; i < meshes_files.Length; ++i)
            {
                CCal3DMeshParser mesh_parser = new CCal3DMeshParser();
                mesh_parser.load(meshes_files[i], context);
                createMesh(rootNode, mesh_parser, ref skeleton_parser);
            }
            pushBindSkeleton(rootNode, 0, ref skeleton_parser);

            return rootNode;
        }

    
        private void createMesh(NodeContent rootNode, CCal3DMeshParser mesh_parser, ref Cal3DSkeletonParser skeleton_parser)
        {
            MeshBuilder mesh_builder;
            mesh_builder = MeshBuilder.StartMesh("mesh");
            mesh_builder.MergeDuplicatePositions = true;
      
            //create material
            BasicMaterialContent materialContent = new BasicMaterialContent();
            mesh_builder.SetMaterial(materialContent);

            int tex_coord_data_index = mesh_builder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));
            int weight_data_index = mesh_builder.CreateVertexChannel<BoneWeightCollection>(VertexChannelNames.Weights(0));
            int normal_data_index = mesh_builder.CreateVertexChannel<Vector3>(VertexChannelNames.Normal());
            int[] position_map = new int[mesh_parser.vertices.Count];
            int count = 0;
            foreach (CCal3DMeshParser.TCalVertex vertex in mesh_parser.vertices)
            {
                position_map[count] = mesh_builder.CreatePosition(vertex.position);
                ++count;
            }
            for (int i = 0; i < mesh_parser.indices.Length; ++i)
            {
                int index_cal = mesh_parser.indices[i];
                int pos_index = position_map[index_cal];
                CCal3DMeshParser.TCalVertex vertex = mesh_parser.vertices[index_cal];
                mesh_builder.SetVertexChannelData(tex_coord_data_index, vertex.tex_coord);
                mesh_builder.SetVertexChannelData(normal_data_index, vertex.normal);
                mesh_builder.SetVertexChannelData(weight_data_index, getBoneWeightCollection( ref vertex ));
                mesh_builder.AddTriangleVertex(pos_index);
            }

            MeshContent mesh_content = mesh_builder.FinishMesh();
            // Groups without any geometry are just for transform
            if (mesh_content.Geometry.Count > 0)
            {
                // Add the mesh to the model
                rootNode.Children.Add(mesh_content);
            }
            else
            {
                // Convert to a general NodeContent
                NodeContent node_content = new NodeContent();
                node_content.Name = mesh_content.Name;

                // Add the transform-only node to the model
                rootNode.Children.Add(node_content);
            }

            mesh_builder = null;

        }


        private BoneWeightCollection getBoneWeightCollection( ref CCal3DMeshParser.TCalVertex vertex )
        {
            BoneWeightCollection bone_collection = new BoneWeightCollection();
            for (int i = 0; i < vertex.influences.Length; ++i)
            {
                CCal3DMeshParser.TInfluence influence = vertex.influences[i];
                BoneWeight bone = new BoneWeight("Bone" + influence.boneId, influence.weight);
                bone_collection.Add(bone);
            }
            bone_collection.NormalizeWeights(4);
            return bone_collection;
        }
    
        //string spaces = " ";    
        private void pushBindSkeleton( NodeContent parent, int index, ref Cal3DSkeletonParser skeleton )
        {
            if (index < skeleton.list_nodes.Count)
            {
                BoneContent bone = new BoneContent();
                Cal3DSkeletonParser.TNode node = skeleton.list_nodes[index];
                bone.Name = "Bone" + node.id;
                //warn(spaces + "Bone '" + node.name + "' (" + index + ") - " + bone.Name);
                bone.Transform = Matrix.CreateFromQuaternion(node.orientation) * Matrix.CreateTranslation(node.position);

                //spaces = "    " + spaces;
                for (int i = 0; i < node.childrens.Count; ++i)
                {
                    int id_children = node.childrens[i];
                    //warn("   - Child: '" + skeleton.list_nodes[id_children].name + "'");
                    pushBindSkeleton(bone, id_children, ref skeleton);
                }
                //spaces = spaces.Substring(4);
                parent.Children.Add(bone);
            }
        }
    }
}
